#[derive(Debug)]
pub struct Config {
    pub file_path: String,
    pub lines: usize,
}

impl Config {
    pub fn new(args: &[String]) -> Config {
        if args.len() < 2 {
            panic!("usage: exe file_path [last_lines]");
        }

        let file_path = args[1].clone();
        let mut lines: usize = 10;

        if args.len() >= 3 {
            lines = args[2].clone().parse().unwrap();
        }

        Config {
            file_path,
            lines,
        }
    }
}

use std::fs::File;
use std::io::{BufReader, BufRead, Read, Seek, SeekFrom};

fn file_line_count(file_path: & str) -> usize {
    let file = File::open(file_path).unwrap();
    let reader = BufReader::new(file);

    reader.lines().count()
}

pub fn cache_process(config: &Config) {
    let f = File::open(&config.file_path).unwrap();
    let reader = BufReader::new(f);

    let cnt = file_line_count(&config.file_path);

    for (nr, line) in reader.lines().enumerate() {
        if cnt <= config.lines || nr >= (cnt - config.lines) { 
            println!("{}", line.unwrap());
        }
    }
}

fn seek_last_n_lines(f: &mut File, n: usize) {
    f.seek(SeekFrom::End(0)).unwrap();

    let mut b: [u8; 1] = [0];
    let mut num = 0;
    loop {
        let r = f.seek(SeekFrom::Current(-1)).unwrap();
        if r > 0 {
            f.read(&mut b).unwrap();
            if b[0] as char == '\n' {
                num += 1;
            }
            if num > n {
                break;
            } else {
                f.seek(SeekFrom::Current(-1)).unwrap();
            }
        } else {
            break;
        }
    }
}

pub fn seek_process(config: &Config) {
    let mut f = File::open(&config.file_path).unwrap();
    
    seek_last_n_lines(&mut f, config.lines);
    
    let reader = BufReader::new(f);

    for line in reader.lines() {
        println!("{}", line.unwrap());
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    #[should_panic(expected = "usage: exe file_path [last_lines]")]
    fn config_less_args() {
        let _ = Config::new(&[String::from("program")]);
    }

    #[test]
    fn config_new() {
        let cfg = Config::new(&[String::from("program"), String::from("./src/demo/poem.txt")]);
        assert_eq!(String::from("./src/demo/poem.txt"), cfg.file_path);
        assert_eq!(10, cfg.lines);

        let cfg = Config::new(&[String::from("program"), 
                                String::from("./src/demo/poem.txt"),
                                String::from("20")]);
        assert_eq!(20, cfg.lines);
    }

    #[test]
    fn get_lines_count() {
        let cnt = file_line_count(&String::from("./src/demo/poem.txt"));
        assert_eq!(9, cnt);
    }

    #[test]
    fn file_seek_last_n_line() {
        let mut f = File::open(&String::from("./src/demo/poem.txt")).unwrap();

        seek_last_n_lines(&mut f, 1);
        let reader = BufReader::new(&f);
        let cnt = reader.lines().count();
        assert_eq!(cnt, 1);

        seek_last_n_lines(&mut f, 8);
        let reader = BufReader::new(&f);
        let cnt = reader.lines().count();
        assert_eq!(cnt, 8);

        seek_last_n_lines(&mut f, 3);
        let reader = BufReader::new(&f);
        let cnt = reader.lines().count();
        assert_eq!(cnt, 3);

        seek_last_n_lines(&mut f, 20);
        let reader = BufReader::new(&f);
        let cnt = reader.lines().count();
        assert_eq!(cnt, 9);

        seek_last_n_lines(&mut f, 5);
        let reader = BufReader::new(&f);
        let cnt = reader.lines().count();
        assert_eq!(cnt, 5);
    }
}
